package uk.ac.lkl.lpp.xml;



import java.io.File;
import java.io.IOException;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import uk.ac.lkl.lpp.LondonPedagogyPlanner;
import uk.ac.lkl.lpp.PedagogyPlan;
import uk.ac.lkl.lpp.PedagogyPlanProcessor;

import uk.ac.lkl.lpp.xml.XMLException;
import uk.ac.lkl.lpp.model.xml.XMLPedagogyPlanModelProcessor;
import uk.ac.lkl.lpp.ui.xml.XMLPedagogyPlanUIProcessor;


/**
 * A processor of a <code>PedagogyPlan</code> that uses XML as its output
 * format.
 * 
 * This class contains references to the model and view XML processors and calls
 * these as appropriate.
 * 
 * @author Darren Pearce
 * @version $Revision$
 * @version $Date$
 * 
 */
public class XMLPedagogyPlanProcessor extends PedagogyPlanProcessor {

    /**
     * An XML processor for the plan's model.
     * 
     */
    private XMLPedagogyPlanModelProcessor modelProcessor;

    /**
     * An XML processor for the plan's UI.
     * 
     */
    private XMLPedagogyPlanUIProcessor uiProcessor;


    /**
     * Create a new instance.
     * 
     * The model and UI processors are set to XML variants automatically.
     * 
     */
    public XMLPedagogyPlanProcessor() {
        modelProcessor = new XMLPedagogyPlanModelProcessor();
        uiProcessor = new XMLPedagogyPlanUIProcessor();
    }


    /**
     * Write the given plan to a file in XML format.
     * 
     * Question: add document.createProcessingInstruction(arg0, arg1); to
     * include stylesheet?
     * 
     * @param plan
     *            the plan to write
     * @param file
     *            the file to write it to
     * 
     */
    public void writeToFile(PedagogyPlan plan, File file) {
        Document document = createNewDocument();

        Element pedagogyPlanElement = document.createElement("PedagogyPlan");
        document.appendChild(pedagogyPlanElement);

        modelProcessor.convertDataToElements(
                                             plan,
                                             document,
                                             pedagogyPlanElement);
        // uiProcessor.convertDataToElements(plan, document,
        // pedagogyPlanElement);

        outputDocument(document, file);
    }


    /**
     * Question: why isn't the factory a static singleton?
     * 
     * @return
     */
    private Document createNewDocument() {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();

            Document document = builder.newDocument();
            return document;
        }
        catch (ParserConfigurationException e) {
            // todo: rethrow own error
            throw new IllegalArgumentException("Unable to create factory");
        }
    }


    /**
     * Question: shouldn't we check if file exists? e.g. add a boolean 'force'
     * argument, through FileExistsException if true, ask user, etc. Question:
     * should we throw our own excection on failure?
     * 
     * @param document
     * @param file
     */
    private void outputDocument(Document document, File file) {
        try {
            DOMSource source = new DOMSource(document);

            String filename = file.getName();
            if (!filename.endsWith(".lpp")) {
                filename += ".lpp";
                file = new File(file.getParent(), filename);
            }

            StreamResult result = new StreamResult(file);
            Transformer transformer = createTransformer();
            transformer.transform(source, result);
        }
        catch (TransformerException e) {
            // HACK do a lot better here since means data is not saved
            throw new IllegalArgumentException(
                    "Transformer exception while transforming");
        }
    }


    /**
     * Question: why not cache transformer? make it a static singleton.
     * Question: rename method to getTransformer?
     * 
     * @return
     */
    private Transformer createTransformer() {
        try {
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            return transformer;
        }
        catch (TransformerConfigurationException e) {
            // HACK do a lot better here since means data is not saved
            throw new IllegalArgumentException(
                    "Transformer Configuration Exception");
        }
    }


    /**
     * Read a plan from the given file.
     * 
     * <p>
     * Question: why isn't the factory a static singleton?
     * <p>
     * <p>
     * Question: why are we mixing model and view?
     * <p>
     * 
     * This takes the given <code>PedagogyPlan</code> and sets its model and
     * view using the data held in the file.
     * 
     */
    @Override
    public void readFromURI(PedagogyPlan plan, URI uri) throws XMLException {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(uri.toString());

            Element documentElement = document.getDocumentElement();

            modelProcessor.convertElementsToData(plan, documentElement);

            ClassLoader cl = LondonPedagogyPlanner.class.getClassLoader();
            URL url = cl.getResource("resources/data/view.xml");
            URI viewURI = url.toURI();

            // a bit hacky
            plan.getUI().clearTabs();

            Document viewDocument = builder.parse(viewURI.toString());
            uiProcessor.convertElementsToData(plan, viewDocument.getDocumentElement());
        }
        catch (URISyntaxException e) {
            throw new XMLException(e);
        }
        catch (ParserConfigurationException e) {
            throw new XMLException(e);
        }
        catch (SAXException e) {
            throw new XMLException(e);
        }
        catch (IOException e) {
            throw new XMLException(e);
        }
    }
}
